// 
//  Author:
//    Anton Lauridsen anton at lauridsen decimal-separator info
// 
//  Copyright (c) 2009, Anton Lauridsen
// 
//  All rights reserved.
// 
//  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in
//       the documentation and/or other materials provided with the distribution.
//     * Neither the name of the [ORGANIZATION] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
// 
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
//  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
//  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
//  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
//  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 

using System;
using System.IO;
using System.Xml;
using System.Collections.Generic;
using OpenMetaverse;

namespace OpenFuse.Backend
{
	public class TaskInventory : List<InventoryItem>
	{
		public UUID Id { get; set; }
		
		public TaskInventory() : base()
		{
		}
	}
	
	public class BackendPrimitive : OpenMetaverse.Primitive
	{
		public TaskInventory Inventory { get; set; }

		public BackendPrimitive(Primitive p) : base(p)
		{
		}
		
		#region XML Reading
		private Color4 ReadColor4(XmlReader reader)
		{
			Color4 color = new Color4(0, 0, 0, 0);
			reader.ReadStartElement();
			int elemCount = 0;
			while(!reader.EOF && elemCount<4)
			{
				if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "R")
				{
					color.R = reader.ReadElementContentAsFloat();	
					elemCount++;
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "G")
				{
					color.G = reader.ReadElementContentAsFloat();	
					elemCount++;
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "B")
				{
					color.B = reader.ReadElementContentAsFloat();	
					elemCount++;
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "A")
				{
					color.A = reader.ReadElementContentAsFloat();	
					elemCount++;
				}
			}
			reader.ReadEndElement();
			
			return color;
		}

		private Quaternion ReadQuaternion(XmlReader reader)
		{
			Quaternion quatern = new Quaternion(0.0f, 0.0f, 0.0f, 0.0f);
			reader.ReadStartElement();
			int elemCount = 0;

			while(!reader.EOF && elemCount<4)
			{
				if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "X")
				{
					quatern.X = reader.ReadElementContentAsFloat();	
					elemCount++;
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Y")
				{
					quatern.Y = reader.ReadElementContentAsFloat();	
					elemCount++;
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Z")
				{
					quatern.Z = reader.ReadElementContentAsFloat();	
					elemCount++;
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "W")
				{
					quatern.Z = reader.ReadElementContentAsFloat();	
					elemCount++;
				}
			}
			reader.ReadEndElement();
			
			return quatern;
		}

		private Vector3 ReadVector3(XmlReader reader)
		{
			Vector3 vector = new Vector3(0.0f);
			reader.ReadStartElement();
			int elemCount = 0;

			while(!reader.EOF && elemCount<3)
			{
				if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "X")
				{
					vector.X = reader.ReadElementContentAsFloat();	
					elemCount++;
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Y")
				{
					vector.Y = reader.ReadElementContentAsFloat();	
					elemCount++;
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Z")
				{
					vector.Z = reader.ReadElementContentAsFloat();	
					elemCount++;
				}
			}
			reader.ReadEndElement();
			
			return vector;
		}
		
		private UUID ReadUuid(XmlReader reader)
		{
			UUID result = UUID.Zero;
			
			reader.ReadStartElement();
			if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "UUID")
			{
				result = new UUID(reader.ReadElementContentAsString());	
			}
			reader.ReadEndElement();
			return result;
		}
		
		private InventoryItem ReadTaskInventoryItem(XmlReader reader)
		{
			InventoryItem item = new InventoryItem(UUID.Random());

			reader.ReadStartElement();
			bool done = false;
			while(!reader.EOF && !done)
			{
				if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "AssetID")
				{
					item.AssetUUID = ReadUuid(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "BasePermissions")
				{
					item.Permissions.BaseMask = (PermissionMask)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "CreationDate")
				{
					item.CreationDate = Utils.UnixTimeToDateTime(reader.ReadElementContentAsInt());
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "CreatorID")
				{
					item.CreatorID = ReadUuid(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Description")
				{
					item.Description = reader.ReadElementContentAsString();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "EveryonePermissions")
				{
					item.Permissions.EveryoneMask = (PermissionMask)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Flags")
				{
					item.Flags = (uint)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "GroupID")
				{
					item.GroupID = ReadUuid(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "GroupPermisssions")
				{
					item.Permissions.GroupMask = (PermissionMask)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "InvType")
				{
					item.InventoryType = (InventoryType)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "ItemID")
				{
					//TODO .. handle readonly situation of item.UUID
					//item.UUID = ReadUuid(reader);
					ReadUuid(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "LastOwnerID")
				{
					ReadUuid(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Name")
				{
					item.Name = reader.ReadElementContentAsString();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "NextPermissions")
				{
					item.Permissions.NextOwnerMask = (PermissionMask)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "OwnerID")
				{
					this.OwnerID = ReadUuid(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "CurrentPermissions")
				{
					item.Permissions.OwnerMask = (PermissionMask)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "ParentID")
				{
					//TODO Handle datatype mismatch of ParentId
					this.ParentID = 0; 
					ReadUuid(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "ParentPartID")
				{
					//TODO should we Handle ParentPartId
					ReadUuid(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PermsGranter")
				{
					//TODO should we handle permsgranter
					ReadUuid(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PermsMask")
				{
					reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Type")
				{
					reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "TaskInventoryItem")
				{
					done = true;
				}
				else
				{
					Console.WriteLine("ReadTaskInventoryItem:" + reader.NodeType.ToString() + "-" + reader.LocalName);
					reader.Read();
				}
			}
			reader.ReadEndElement();
			return item;
		}
		
		private void ReadTaskInventory(XmlReader reader)
		{
			reader.ReadStartElement();
			bool done = false;
			while(!reader.EOF && !done)
			{
				if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "TaskInventoryItem")
				{
					this.Inventory.Add(ReadTaskInventoryItem(reader));
				}
				else if(reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "TaskInventory")
				{
					done = true;
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "ObjectFlags")
				{
					// the TaskInventory node is empty, return now, or the parser will get out of sync
					return;
				}
				else
				{
					Console.WriteLine("ReadTaskInventory:" + reader.NodeType.ToString() + "-" + reader.LocalName);
					reader.Read();
				}
			}
			reader.ReadEndElement();
		}
		
		private void ReadShape(BackendPrimitive prim, XmlReader reader)
		{
			reader.ReadStartElement();
			bool done = false;
			while(!reader.EOF && !done)
			{
				if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PathBegin")
					prim.PrimData.PathBegin = Primitive.UnpackBeginCut(ushort.Parse(reader.ReadElementContentAsString()));
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PathCurve")
					prim.PrimData.PathCurve = (PathCurve)byte.Parse(reader.ReadElementContentAsString());
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PathEnd")
					prim.PrimData.PathEnd = Primitive.UnpackEndCut(ushort.Parse(reader.ReadElementContentAsString()));
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PathRadiusOffset")
					prim.PrimData.PathRadiusOffset = Primitive.UnpackPathTwist(sbyte.Parse(reader.ReadElementContentAsString()));
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PathRevolutions")
					prim.PrimData.PathRevolutions = Primitive.UnpackPathRevolutions(byte.Parse(reader.ReadElementContentAsString()));
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PathScaleX")
					prim.PrimData.PathScaleX = Primitive.UnpackPathScale(byte.Parse(reader.ReadElementContentAsString()));
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PathScaleY")
					prim.PrimData.PathScaleY = Primitive.UnpackPathScale(byte.Parse(reader.ReadElementContentAsString()));
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PathShearX")
					prim.PrimData.PathShearX = Primitive.UnpackPathShear(sbyte.Parse(reader.ReadElementContentAsString()));
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PathShearY")
					prim.PrimData.PathShearY = Primitive.UnpackPathShear(sbyte.Parse(reader.ReadElementContentAsString()));
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PathSkew")
					prim.PrimData.PathSkew = Primitive.UnpackPathTwist(sbyte.Parse(reader.ReadElementContentAsString()));
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PathTaperX")
					prim.PrimData.PathTaperX = Primitive.UnpackPathTaper(sbyte.Parse(reader.ReadElementContentAsString()));
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PathTaperY")
					prim.PrimData.PathTaperY = Primitive.PackPathTaper(reader.ReadElementContentAsFloat());
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PathTwist")
					prim.PrimData.PathTwist = Primitive.PackPathTwist(reader.ReadElementContentAsFloat());
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PathTwistBegin")
					prim.PrimData.PathTwistBegin = Primitive.PackPathTwist(reader.ReadElementContentAsFloat());
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "PCode")
					prim.PrimData.PCode = (PCode)byte.Parse(reader.ReadElementContentAsString());
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "ProfileBegin")
					prim.PrimData.ProfileBegin = Primitive.UnpackBeginCut(ushort.Parse(reader.ReadElementContentAsString()));
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "ProfileEnd")
					prim.PrimData.ProfileEnd = Primitive.UnpackEndCut(ushort.Parse(reader.ReadElementContentAsString()));
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "ProfileHollow")
					prim.PrimData.ProfileHollow = Primitive.UnpackProfileHollow(ushort.Parse(reader.ReadElementContentAsString()));
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Scale")
					prim.Scale = ReadVector3(reader);
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "State")
					prim.PrimData.State = byte.Parse(reader.ReadElementContentAsString());
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "ProfileShape")
					reader.ReadElementContentAsString();  // ignore, duplicate of 'ProfileCurve'
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "HollowShape")
					prim.PrimData.ProfileHole = (HoleType)Enum.Parse(typeof(HoleType), reader.ReadElementContentAsString());
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "ProfileCurve")
					prim.PrimData.profileCurve = byte.Parse(reader.ReadElementContentAsString());
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "TextureEntry")
				{
					// there is a bug in MONO, hence we cannot use reader.ReadElementContentAsBase64
					// TODO convert to ReadElementContentAsBase64 when the fix is generally available
					string rawBase64 = reader.ReadElementContentAsString();
					byte[] te = System.Convert.FromBase64String(rawBase64);
					prim.Textures = new TextureEntry(te, 0, te.Length);
				}
//
//            // FIXME: ExtraParams
//            writer.WriteStartElement("ExtraParams"); writer.WriteEndElement();
//
//            writer.WriteEndElement();
				else if(reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "Shape")
				{
					done = true;
				}
				else
				{
					Console.WriteLine("ReadShape:" + reader.NodeType.ToString() + "-" + reader.LocalName);
					reader.Read();
				}
			}
			reader.ReadEndElement();
		}

		public BackendPrimitive(XmlReader reader) : base()
		{
			this.Properties = new ObjectProperties();
			reader.ReadStartElement();

			bool done = false;
			while(!reader.EOF && !done)
			{
				if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "CreatorID")
				{
					this.Properties.CreatorID = ReadUuid(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "FolderID")
				{
					this.Properties.FolderID = ReadUuid(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "InventorySerial")
				{
					this.Properties.InventorySerial = (short)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "TaskInventory")
				{
					this.Inventory = new TaskInventory();
					ReadTaskInventory(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "ObjectFlags")
				{
					this.Flags = (PrimFlags)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "UUID")
				{
					this.ID = ReadUuid(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "LocalId")
				{
					// bogus, local id will be assigned when we create/rez the object in the sim
					uint.TryParse(reader.ReadElementContentAsString(), out this.LocalID);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Name")
				{
					this.Properties.Name = reader.ReadElementContentAsString();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Material")
				{
					this.PrimData.Material = (Material)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "RegionHandle")
				{
					ulong.TryParse(reader.ReadElementContentAsString(), out this.RegionHandle);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "ScriptAccessPin")
				{
					// TODO should we handle script access pin
					reader.ReadElementContentAsString();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "GroupPosition")
				{
					// TODO need to handle position of child elements
					this.Position = ReadVector3(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "OffsetPosition")
				{
					// children position are best kept as offsets to the father
					this.Position -= ReadVector3(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "RotationOffset")
				{
					this.Rotation = ReadQuaternion(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Velocity")
				{
					this.Velocity = ReadVector3(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "RotationalVelocity")
				{
					// this value exists inside the Oar only
					ReadVector3(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "AngularVelocity")
				{
					this.AngularVelocity = ReadVector3(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Acceleration")
				{
					this.Acceleration = ReadVector3(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Description")
				{
					this.Properties.Description = reader.ReadElementContentAsString();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Color")
				{
					this.TextColor = ReadColor4(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Text")
				{
					this.Text = reader.ReadElementContentAsString();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "SitName")
				{
					this.Properties.SitName = reader.ReadElementContentAsString();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "TouchName")
				{
					this.Properties.TouchName = reader.ReadElementContentAsString();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "LinkNum")
				{
					// TODO Handle linknum, ensure correct sequencing of links
					reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "ClickAction")
				{
					this.ClickAction = (ClickAction)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Shape")
				{
					ReadShape(this, reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Scale")
				{
					this.Scale = ReadVector3(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "ParentID")
				{
					this.ParentID = uint.Parse(reader.ReadElementContentAsString());
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "CreationDate")
				{
					this.Properties.CreationDate = Utils.UnixTimeToDateTime(reader.ReadElementContentAsInt());
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Category")
				{
					this.Properties.Category = (ObjectCategory)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "SalePrice")
				{
					this.Properties.SalePrice = reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "ObjectSaleType")
				{
					this.Properties.SaleType = (SaleType)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "OwnershipCost")
				{
					this.Properties.OwnershipCost = reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "GroupID")
				{
					this.GroupID = ReadUuid(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "OwnerID")
				{
					this.OwnerID = ReadUuid(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "LastOwnerID")
				{
					this.Properties.LastOwnerID = ReadUuid(reader);
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "BaseMask")
				{
					this.Properties.Permissions.BaseMask = (PermissionMask)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "OwnerMask")
				{
					this.Properties.Permissions.OwnerMask = (PermissionMask)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "GroupMask")
				{
					this.Properties.Permissions.GroupMask = (PermissionMask)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "EveryoneMask")
				{
					this.Properties.Permissions.EveryoneMask = (PermissionMask)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "NextOwnerMask")
				{
					this.Properties.Permissions.NextOwnerMask = (PermissionMask)reader.ReadElementContentAsInt();
				}
				else if(reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "SceneObjectPart")
				{
					done = true;
				}
				else
				{
					// ignored values
					if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "UpdateFlag")
						reader.ReadElementContentAsString();
					else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "SitTargetOrientation")
						ReadVector3(reader);
					else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "SitTargetPosition")
						ReadVector3(reader);
					else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "SitTargetOrientationLL")
						ReadQuaternion(reader);
					else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "SitTargetPositionLL")
						ReadVector3(reader);
					else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "Flags")
						reader.ReadElementContentAsString();
					else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "SitTargetAvatar")
						ReadUuid(reader);
					else {
						Console.WriteLine("BackendPrimitive:" + reader.NodeType.ToString() + "-" + reader.LocalName);
						reader.Read();
					}
				}
			}
			reader.ReadEndElement();
		}
		#endregion XML Reading
	}
	
	
	public class LinkSet
	{
        private BackendPrimitive _root;
		
		public BackendPrimitive Root
		{
			get { return _root; }
			private set 
			{
				Children.Add(value);
				_root = value;
			}
		}
		public List<BackendPrimitive> Children { private set; get; }
		
		public IList<BackendPrimitive> TrueChildren
		{
			get { return Children.FindAll(p => p.ParentID != 0); }
		}

		public LinkSet(SlSession session)
		{
			Children = new List<BackendPrimitive>();
		}

		public LinkSet(SlSession session, BackendPrimitive prim) : this(session)
		{
			Root = prim;
		}
		
		public static LinkSet AutoPopulate(SlSession session, BackendPrimitive prim)
		{
			LinkSet linkSet = new LinkSet(session);
			
			uint rootPrimID = Utilities.FindRootPrimitive(session, prim);
			
            linkSet.Root = new BackendPrimitive(session.GridClient.Network.CurrentSim.ObjectsPrimitives.Find(p => p.LocalID == rootPrimID));

			IList<Primitive> children = session.GridClient.Network.CurrentSim.ObjectsPrimitives.FindAll(p => p.ParentID == linkSet.Root.LocalID);
			
			foreach(Primitive p in children)
			{
				if(p.Properties == null)
					if(!Utilities.RequestObjectProperties(session, p))
						session.Logger.ErrorFormat("Failed to retrieve properties for {0}", p.ID);
				linkSet.Children.Add(new BackendPrimitive(p));
			}
			
			linkSet.SortChildren();
			
			return linkSet;
		}
		
		private void ReadChildren(XmlReader reader)
		{
			reader.ReadStartElement();
			
			bool done = false;
			while(!reader.EOF && !done)
			{
				if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "SceneObjectPart")
				{
					Children.Add(new BackendPrimitive(reader));
				}
				else if(reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "OtherParts")
				{
					done = true;
				}
				else if(reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "SceneObjectGroup")
				{
					// the collection is completely empty, the ReadElement at the end of the method
					// will bring the reading out of sync, hence the return, rather than done = true
					return;
				}
				else
				{
					Console.WriteLine("ReadChildren:" + reader.NodeType.ToString() + "-" + reader.LocalName);
					reader.Read();
				}
			}
			reader.ReadEndElement();
		}
		
		public LinkSet(SlSession session, byte[] xmlByteBlob) : this(session)
		{
			XmlReaderSettings settings = new XmlReaderSettings();
			settings.IgnoreComments = true;
			settings.IgnoreWhitespace = true;
			settings.IgnoreProcessingInstructions = true;
			
			using(XmlReader reader = XmlReader.Create(new MemoryStream(xmlByteBlob), settings))
			{
				reader.Read();
				
				if(reader.NodeType != XmlNodeType.Element || reader.LocalName != "SceneObjectGroup")
					throw new Exception("XML stream does not represent a SceneObjectGroup");
				
				bool done = false;
				while(!reader.EOF && !done)
				{
					if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "SceneObjectPart")
					{
						Root = new BackendPrimitive(reader);
					}
					else if(reader.NodeType == XmlNodeType.Element && reader.LocalName == "OtherParts")
					{
						ReadChildren(reader);
					}
					else if(reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "SceneObjectGroup")
					{
						done = true;
					}
					else
					{
						Console.WriteLine("LinkSet:" + reader.NodeType.ToString() + "-" + reader.LocalName);
						reader.Read();
					}
				}
			}
			SortChildren();
		}
		
		public void SortChildren()
		{
			Children.Sort(delegate(BackendPrimitive l, BackendPrimitive r)
			    {
					if(l.ParentID == 0)
						// both l and r are root they are equal, if l root it it greater
						if(r.ParentID == 0)	return 0; else return -1;
						// if r is root but not l, then l is lesss
					else if(r.ParentID == 0) return 1;
					// none are root, so order by localId
					else if(l.LocalID < r.LocalID) return -1;
					else if(l.LocalID > r.LocalID) return 1;
				    else return 0;
				});
		}
		
		public void DownloadTaskInventories(SlSession session)
		{
			foreach(BackendPrimitive p in Children) {
				if((p.Flags & PrimFlags.InventoryEmpty) == 0)
					p.Inventory = Utilities.DownloadTaskInventory(session, p);
			}
		}
		
		public InventoryItem FindInventoryItemByAssetID(UUID assetID)
		{
			InventoryItem item = null;
			
			foreach(BackendPrimitive be in Children) {
				item = be.Inventory.Find(i => i.AssetUUID == assetID);
				if(item != null) break;
			}
			return item;
		}
	}
}
